package ru.ifmo.rybakov.muclumon.analysis;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.ifmo.rybakov.muclumon.PropertyType;
import ru.ifmo.rybakov.muclumon.PropertyValue;
import ru.ifmo.rybakov.muclumon.Task;

/**
 * This simple algorithm divides all the tasks into groups using values of their
 * {@link PropertyType#GROUP} properties. Mostly that should be string properties.
 * Division is required as linear regression models seems to be completely different
 * for different group property values and must not be analyzed together.
 * @author gleb.rybakov
 *
 */
public class TaskGroupClusteringAlgorithm {
	
	private static final Logger logger = LoggerFactory.getLogger(TaskGroupClusteringAlgorithm.class);
	
	static class GroupValue {
		private PropertyValue value;
		
		public GroupValue(PropertyValue value) {
			this.value = value;
		}

		/* (non-Javadoc)
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj) {
			if (obj instanceof GroupValue) {
				GroupValue other = (GroupValue) obj;
				return value.getProperty().equals(other.value.getProperty()) &&
					value.getValue().equals(other.value.getValue());
			}
			return super.equals(obj);
		}

		/* (non-Javadoc)
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
			return value.getProperty().hashCode();
		}
	}
	
	public ICluster[] run(Task[] tasks) {
		Map<Set<GroupValue>, Set<Task>> groupMapping = new HashMap<Set<GroupValue>, Set<Task>>();
		
		for (Task t : tasks) {
			Set<GroupValue> groupValues = getGroupValues(t);
			Set<Task> group = groupMapping.get(groupValues);
			if (group != null) {
				group.add(t);
			} else {
				group = new HashSet<Task>();
				group.add(t);
				groupMapping.put(groupValues, group);
			}
		}
		
		// here we have distinct groups of tasks
		ICluster[] clusters = new ICluster[groupMapping.values().size()];
		int i = 0;
		for (Entry<Set<GroupValue>, Set<Task>> entry : groupMapping.entrySet()) {
			clusters[i] = new TaskCluster(entry.getValue());
			logger.info("Group cluster found (size={})", clusters[i].getObjects().size());
			Set<GroupValue> groupValues = entry.getKey();
			for (GroupValue gv : groupValues) {
				System.out.println(gv.value.getProperty().getName() + "\t" + 
						gv.value.getValue().toString());
			}
			i++;
		}
		return clusters;
	}

	/**
	 * Returns subset of task properties that have {@link PropertyType#GROUP} type.
	 * @param task The task to be inspected
	 * @return Set of {@link PropertyType#GROUP} properties
	 */
	public static Set<GroupValue> getGroupValues(Task task) {
		Set<GroupValue> groupValues = new HashSet<GroupValue>();
		Collection<PropertyValue> values = task.getPropertyValues();
		for (PropertyValue pv : values) {
			if (pv.getProperty().getType() == PropertyType.GROUP) { // use only GROUP roperties
				groupValues.add(new GroupValue(pv));
			}
		}
		return groupValues;
	}
}
